﻿using System.Collections.Generic;
using System.Collections;
using UnityEngine;

namespace FairyGUI
{
	public delegate void TimerCallback(object param);

	/// <summary>
	/// 
	/// </summary>
	public class Timers
	{
		public static int repeat;
		public static float time;

		public static bool catchCallbackExceptions = false;

		Dictionary<TimerCallback, Anymous_T> _items;
		Dictionary<TimerCallback, Anymous_T> _toAdd;
		List<Anymous_T> _toRemove;
		List<Anymous_T> _pool;

		TimersEngine _engine;
		GameObject gameObject;

		private static Timers _inst;
		public static Timers inst
		{
			get
			{
				if (_inst == null)
					_inst = new Timers();
				return _inst;
			}
		}

		public Timers()
		{
			_inst = this;
			gameObject = new GameObject("[FairyGUI.Timers]");
			gameObject.hideFlags = HideFlags.HideInHierarchy;
			gameObject.SetActive(true);
			Object.DontDestroyOnLoad(gameObject);

			_engine = gameObject.AddComponent<TimersEngine>();

			_items = new Dictionary<TimerCallback, Anymous_T>();
			_toAdd = new Dictionary<TimerCallback, Anymous_T>();
			_toRemove = new List<Anymous_T>();
			_pool = new List<Anymous_T>(100);
		}

		public void Add(float interval, int repeat, TimerCallback callback)
		{
			Add(interval, repeat, callback, null);
		}

		/**
		 * @interval in seconds
		 * @repeat 0 indicate loop infinitely, otherwise the run count
		 **/
		public void Add(float interval, int repeat, TimerCallback callback, object callbackParam)
		{
			if (callback == null)
			{
				Debug.LogWarning("timer callback is null, " + interval + "," + repeat);
				return;
			}

			Anymous_T t;
			if (_items.TryGetValue(callback, out t))
			{
				t.set(interval, repeat, callback, callbackParam);
				t.elapsed = 0;
				t.deleted = false;
				return;
			}

			if (_toAdd.TryGetValue(callback, out t))
			{
				t.set(interval, repeat, callback, callbackParam);
				return;
			}

			t = GetFromPool();
			t.interval = interval;
			t.repeat = repeat;
			t.callback = callback;
			t.param = callbackParam;
			_toAdd[callback] = t;
		}

		public void CallLater(TimerCallback callback)
		{
			Add(0.001f, 1, callback);
		}

		public void CallLater(TimerCallback callback, object callbackParam)
		{
			Add(0.001f, 1, callback, callbackParam);
		}

		public void AddUpdate(TimerCallback callback)
		{
			Add(0.001f, 0, callback);
		}

		public void AddUpdate(TimerCallback callback, object callbackParam)
		{
			Add(0.001f, 0, callback, callbackParam);
		}

		public void StartCoroutine(IEnumerator routine)
		{
			_engine.StartCoroutine(routine);
		}

		public bool Exists(TimerCallback callback)
		{
			if (_toAdd.ContainsKey(callback))
				return true;

			Anymous_T at;
			if (_items.TryGetValue(callback, out at))
				return !at.deleted;

			return false;
		}

		public void Remove(TimerCallback callback)
		{
			Anymous_T t;
			if (_toAdd.TryGetValue(callback, out t))
			{
				_toAdd.Remove(callback);
				ReturnToPool(t);
			}

			if (_items.TryGetValue(callback, out t))
				t.deleted = true;
		}

		private Anymous_T GetFromPool()
		{
			Anymous_T t;
			int cnt = _pool.Count;
			if (cnt > 0)
			{
				t = _pool[cnt - 1];
				_pool.RemoveAt(cnt - 1);
				t.deleted = false;
				t.elapsed = 0;
			}
			else
				t = new Anymous_T();
			return t;
		}

		private void ReturnToPool(Anymous_T t)
		{
			t.callback = null;
			_pool.Add(t);
		}

		public void Update()
		{
			float dt = Time.unscaledDeltaTime;
			Dictionary<TimerCallback, Anymous_T>.Enumerator iter;

			if (_items.Count > 0)
			{
				iter = _items.GetEnumerator();
				while (iter.MoveNext())
				{
					Anymous_T i = iter.Current.Value;
					if (i.deleted)
					{
						_toRemove.Add(i);
						continue;
					}

					i.elapsed += dt;
					if (i.elapsed < i.interval)
						continue;

					i.elapsed -= i.interval;
					if (i.elapsed < 0 || i.elapsed > 0.03f)
						i.elapsed = 0;

					if (i.repeat > 0)
					{
						i.repeat--;
						if (i.repeat == 0)
						{
							i.deleted = true;
							_toRemove.Add(i);
						}
					}
					repeat = i.repeat;
					if (i.callback != null)
					{
						if (catchCallbackExceptions)
						{
							try
							{
								i.callback(i.param);
							}
							catch (System.Exception e)
							{
								i.deleted = true;
								Debug.LogWarning("FairyGUI: timer(internal=" + i.interval + ", repeat=" + i.repeat + ") callback error > " + e.Message);
							}
						}
						else
							i.callback(i.param);
					}
				}
				iter.Dispose();
			}

			int len = _toRemove.Count;
			if (len > 0)
			{
				for (int k = 0; k < len; k++)
				{
					Anymous_T i = _toRemove[k];
					if (i.deleted && i.callback != null)
					{
						_items.Remove(i.callback);
						ReturnToPool(i);
					}
				}
				_toRemove.Clear();
			}

			if (_toAdd.Count > 0)
			{
				iter = _toAdd.GetEnumerator();
				while (iter.MoveNext())
					_items.Add(iter.Current.Key, iter.Current.Value);
				iter.Dispose();
				_toAdd.Clear();
			}
		}
	}

	class Anymous_T
	{
		public float interval;
		public int repeat;
		public TimerCallback callback;
		public object param;

		public float elapsed;
		public bool deleted;

		public void set(float interval, int repeat, TimerCallback callback, object param)
		{
			this.interval = interval;
			this.repeat = repeat;
			this.callback = callback;
			this.param = param;
		}
	}

	class TimersEngine : MonoBehaviour
	{
		void Update()
		{
			Timers.inst.Update();
		}
	}
}
